import json  # 导入JSON处理模块，用于序列化和反序列化数据
import sys  # 导入系统相关模块，用于访问解释器变量和函数
import os  # 导入操作系统接口模块，用于环境变量和文件系统操作
import logging  # 导入日志记录模块，用于应用程序日志
from collections.abc import Callable, AsyncIterator  # 导入抽象基类，定义可调用对象和异步迭代器类型
from pathlib import Path  # 导入路径操作模块，提供面向对象的文件系统路径处理
from typing import Any, Dict, List, Optional, Tuple, Union  # 导入类型提示相关类型
from datetime import datetime, timezone, timedelta  # 导入时间日期处理模块，包括时区支持
from contextlib import asynccontextmanager  # 导入异步上下文管理器装饰器
from dataclasses import dataclass  # 导入数据类装饰器，简化类定义

from starlette.applications import Starlette  # 导入Starlette ASGI应用框架
from starlette.responses import JSONResponse, Response  # 导入HTTP响应类型
from starlette.routing import Route, Mount  # 导入路由定义类
from starlette.middleware import Middleware  # 导入中间件基类
from starlette.middleware.cors import CORSMiddleware  # 导入CORS跨域中间件

import mcp.types as types  # 导入MCP协议类型定义
from mcp.server.lowlevel import Server  # 导入MCP底层服务器实现
from mcp.server.sse import SseServerTransport  # 导入MCP的服务器发送事件传输层

from db_mysql_connector import DatabaseConnector  # 导入自定义数据库连接器
from prometheus_connector import PrometheusConnector  # 导入自定义Prometheus监控连接器

# ---------------- Configuration ----------------
CONFIG = {  # 定义全局配置字典
    "LOG_DIR": Path(os.getenv("LOG_DIR", Path(__file__).parent / "logs")),  # 日志目录路径，优先从环境变量读取，否则使用脚本同级logs目录
    "HOST": os.getenv("HOST", "0.0.0.0"),  # 服务器监听地址，默认监听所有接口
    "PORT": int(os.getenv("PORT", "9300")),  # 服务器监听端口，默认9300
    "ALLOWED_ORIGINS": json.loads(os.getenv("ALLOWED_ORIGINS", '["*"]')),  # CORS允许的来源列表，默认允许所有来源
    "BASE_DIR": Path(__file__).parent.resolve(),  # 项目根目录的绝对路径
    "ALLOWED_LOG_PATTERNS": ["*.log", "*.txt"],  # 允许访问的日志文件模式列表
    "MAX_LOG_SIZE": 10 * 1024 * 1024,  # 最大日志文件大小限制，10MB
    "METRIC_QUERY_WINDOW": int(os.getenv("METRIC_QUERY_WINDOW", "3600")),  # 监控指标查询时间窗口，默认1小时（3600秒）
}

# ---------------- Logging Setup ----------------
def setup_logging():  # 定义日志系统配置函数
    """配置日志系统"""  # 函数文档字符串
    log_dir = CONFIG["LOG_DIR"]  # 获取日志目录路径
    log_dir.mkdir(parents=True, exist_ok=True)  # 创建日志目录，如果父目录不存在则一并创建，目录已存在不报错
    
    # Clear old log handlers
    logging.root.handlers = []  # 清空根日志记录器的所有处理器，避免重复配置
    
    # Setup handlers
    handlers = [  # 创建日志处理器列表
        logging.StreamHandler(sys.stderr),  # 添加标准错误输出处理器
    ]
    
    # File handler with rotation
    try:  # 尝试使用旋转文件处理器
        from logging.handlers import RotatingFileHandler  # 导入旋转文件处理器
        file_handler = RotatingFileHandler(  # 创建旋转文件处理器实例
            log_dir / "server.log",  # 日志文件路径
            maxBytes=CONFIG["MAX_LOG_SIZE"],  # 单个日志文件最大大小
            backupCount=5,  # 保留的旧日志文件数量
            encoding="utf-8"  # 文件编码格式
        )
        handlers.append(file_handler)  # 将文件处理器添加到处理器列表
    except ImportError:  # 如果导入失败（某些环境可能不支持）
        logging.warning("RotatingFileHandler not available, using basic FileHandler")  # 记录警告信息
        handlers.append(logging.FileHandler(log_dir / "server.log", encoding="utf-8"))  # 使用基本文件处理器作为备选
    
    # Configure logging
    logging.basicConfig(  # 配置根日志记录器
        level=logging.INFO,  # 设置日志级别为INFO
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",  # 设置日志格式：时间 [级别] 记录器名称: 消息
        handlers=handlers  # 指定使用的处理器列表
    )
    
    # Set specific loggers
    logging.getLogger("uvicorn").setLevel(logging.WARNING)  # 将uvicorn日志级别设置为WARNING，减少冗余信息
    logging.getLogger("asyncio").setLevel(logging.WARNING)  # 将asyncio日志级别设置为WARNING，减少冗余信息

setup_logging()  # 调用日志配置函数，初始化日志系统
log = logging.getLogger("mcp-server")  # 创建名为"mcp-server"的日志记录器实例

# ---------------- Context Management ----------------
class ApplicationContext:  # 定义应用程序上下文管理类
    """应用程序资源中心上下文"""  # 类文档字符串
    def __init__(self):  # 类构造函数
        self.db = DatabaseConnector()  # 创建数据库连接器实例
        self.prom = PrometheusConnector()  # 创建Prometheus连接器实例
        self.initialized = False  # 初始化状态标志，默认为False
    
    async def initialize(self):  # 异步初始化方法
        """异步初始化资源"""  # 方法文档字符串
        if not self.initialized:  # 如果尚未初始化
            await self.db.connect()  # 异步连接数据库
            await self.prom.connect()  # 异步连接Prometheus
            self.initialized = True  # 设置初始化状态为True
    
    async def close(self):  # 异步清理方法
        """清理资源"""  # 方法文档字符串
        if self.initialized:  # 如果已经初始化
            await self.db.close()  # 异步关闭数据库连接
            await self.prom.close()  # 异步关闭Prometheus连接
            self.initialized = False  # 重置初始化状态为False

@asynccontextmanager  # 异步上下文管理器装饰器
async def lifespan(_: Server) -> AsyncIterator[dict]:  # 定义应用生命周期管理函数，参数为Server实例，返回字典的异步迭代器
    """异步上下文管理器，用于管理应用程序生命周期"""  # 函数文档字符串
    ctx = ApplicationContext()  # 创建应用上下文实例
    try:  # 尝试执行初始化
        await ctx.initialize()  # 异步初始化上下文
        log.info("Application context initialized")  # 记录初始化成功日志
        yield {"ctx": ctx}  # 生成包含上下文的字典，供应用使用
    except Exception as e:  # 捕获初始化过程中的异常
        log.critical(f"Lifespan startup failed: {e}", exc_info=True)  # 记录严重错误日志，包含异常堆栈信息
        raise  # 重新抛出异常
    finally:  # 无论是否发生异常都会执行
        await ctx.close()  # 异步清理上下文资源
        log.info("Application context closed")  # 记录清理完成日志

# ---------------- Prompt Management ----------------
@dataclass  # 数据类装饰器，自动生成__init__、__repr__等方法
class PromptTemplate:  # 定义提示模板数据类
    """结构化提示模板"""  # 类文档字符串
    description: str  # 模板描述字段
    template: str  # 模板内容字段
    arguments: List[types.PromptArgument]  # 模板参数列表字段

class PromptRegistry:  # 定义提示模板注册表类
    """提示模板中央注册表"""  # 类文档字符串
    def __init__(self):  # 类构造函数
        self.templates: Dict[str, PromptTemplate] = {}  # 初始化模板字典，键为模板名称，值为PromptTemplate实例
    
    def register(self, name: str, description: str, template: str, arguments: List[types.PromptArgument]):  # 模板注册方法
        """注册新的提示模板"""  # 方法文档字符串
        self.templates[name] = PromptTemplate(  # 在模板字典中存储新的PromptTemplate实例
            description=description,  # 设置描述
            template=template,  # 设置模板内容
            arguments=arguments,  # 设置参数列表
        )
    
    def get(self, name: str) -> Optional[PromptTemplate]:  # 获取模板方法，返回可选的PromptTemplate
        """按名称获取提示模板"""  # 方法文档字符串
        return self.templates.get(name)  # 从字典中获取指定名称的模板，不存在返回None
    
    def list(self) -> List[types.Prompt]:  # 列出所有模板方法，返回Prompt类型列表
        """列出所有可用提示"""  # 方法文档字符串
        return [  # 返回列表推导式结果
            types.Prompt(  # 创建Prompt实例
                name=name,  # 设置名称
                description=tpl.description,  # 设置描述
                arguments=tpl.arguments  # 设置参数列表
            )
            for name, tpl in self.templates.items()  # 遍历所有注册的模板
        ]

# 初始化提示注册表
prompt_registry = PromptRegistry()  # 创建全局提示注册表实例
prompt_registry.register(  # 注册数据库性能诊断提示模板
    name="diagnose_db_performance",  # 模板名称
    description="根据指标生成巡检报告",  # 模板描述
    template=(  # 模板内容，使用多行字符串
        "请根据实例 {instance_id} 在 {time_range} 期间的监控指标，为数据库 '{dbname}' 生成一份详细的巡检报告：\n"
        "## 服务器指标\n"
        "{node_metrics_json}\n\n"
        "## 数据库指标\n"
        "{db_metrics_json}\n"
        "## 报告要求\n"
        "1. 分析CPU、内存、磁盘I/O、连接数等关键指标\n"
        "2. 识别性能瓶颈和异常值\n"
        "3. 评估数据库整体健康状况\n"
        "4. 提供具体的优化建议和改进措施\n"
        "5. 对潜在问题进行风险评估\n\n"
        "请使用专业、清晰的语言，并按照以下结构组织报告：\n"
        "- 数据库基本信息\n"
        "- 摘要：概述数据库状态和主要发现\n"
        "- 详细分析：按指标类别逐一分析\n"
        "- 问题与风险：列出发现的问题和潜在风险\n"
        "- 建议：提供针对性的优化建议\n"
        "- 结论：总结巡检结果"
    ),
    arguments=[  # 模板参数列表
        types.PromptArgument(name="instance_id", description="实例ID", required=True),  # 必需的实例ID参数
        types.PromptArgument(name="time_range", description="时间范围"),  # 可选的时间范围参数
        types.PromptArgument(name="dbname", description="数据库名称", required=True),  # 必需的数据库名称参数
        types.PromptArgument(name="node_metrics_json", description="服务器指标数据"),  # 可选的服务器指标参数
        types.PromptArgument(name="db_metrics_json", description="数据库指标数据"),  # 可选的数据库指标参数
    ]
)

# ---------------- Tool System ----------------
@dataclass  # 数据类装饰器
class ToolDefinition:  # 定义工具定义数据类
    """结构化工具定义"""  # 类文档字符串
    name: str  # 工具名称字段
    description: str  # 工具描述字段
    input_schema: Dict[str, Any]  # 输入参数模式字段，使用JSON Schema格式
    handler: Callable  # 工具处理函数字段，必须是可调用对象

class ToolRegistry:  # 定义工具注册表类
    """可用工具的中央注册表"""  # 类文档字符串
    def __init__(self):  # 类构造函数
        self.tools: Dict[str, ToolDefinition] = {}  # 初始化工具字典，键为工具名称，值为ToolDefinition实例
    
    def register(self, tool: ToolDefinition):  # 工具注册方法
        """注册新工具"""  # 方法文档字符串
        if tool.name in self.tools:  # 如果工具名称已存在
            log.warning(f"Tool {tool.name} already registered, overwriting")  # 记录警告日志
        self.tools[tool.name] = tool  # 在工具字典中存储新的工具定义
    
    def get(self, name: str) -> Optional[ToolDefinition]:  # 获取工具方法，返回可选的ToolDefinition
        """按名称获取工具"""  # 方法文档字符串
        return self.tools.get(name)  # 从字典中获取指定名称的工具，不存在返回None
    
    def list(self) -> List[types.Tool]:  # 列出所有工具方法，返回Tool类型列表
        """列出所有可用工具"""  # 方法文档字符串
        return [  # 返回列表推导式结果
            types.Tool(  # 创建Tool实例
                name=tool.name,  # 设置名称
                description=tool.description,  # 设置描述
                inputSchema=tool.input_schema  # 设置输入模式
            )
            for tool in self.tools.values()  # 遍历所有注册的工具
        ]

# 初始化工具注册表
tool_registry = ToolRegistry()  # 创建全局工具注册表实例

# ---------------- Tool Handlers ----------------
async def query_db_info(ctx: ApplicationContext, args: dict) -> dict:  # 异步函数，查询数据库信息
    """查询数据库信息"""  # 函数文档字符串
    if "dbname" not in args:  # 检查必需参数是否存在
        raise ValueError("缺少必要参数: dbname")  # 抛出参数错误异常
    try:  # 尝试执行数据库查询
        return await ctx.db.get_db_info(args["dbname"])  # 异步调用数据库连接器获取数据库信息
    except Exception as e:  # 捕获所有异常
        log.error(f"数据库查询失败: {e}", exc_info=True)  # 记录错误日志，包含异常堆栈信息
        raise RuntimeError("数据库查询失败") from e  # 抛出运行时错误，保留原始异常信息

async def query_prometheus_metrics(ctx: ApplicationContext, args: dict) -> dict:  # 异步函数，查询Prometheus指标
    """查询普罗米修斯监控数据 (支持多种实例标识)"""  # 函数文档字符串
    # 解析参数
    dbname = args.get("dbname")  # 从参数中获取数据库名称，可能为None
    instance_id = args.get("instance_id")  # 从参数中获取实例ID，可能为None
    start_ts = args.get("start")  # 从参数中获取开始时间戳，可能为None
    end_ts = args.get("end")  # 从参数中获取结束时间戳，可能为None
    
    if not dbname and not instance_id:  # 如果数据库名称和实例ID都不存在
        raise ValueError("必须提供dbname或instance_id")  # 抛出参数错误异常
    
    # 处理时间范围 - 使用时间戳
    if not end_ts:  # 如果没有提供结束时间戳
        end_ts = int(datetime.now().timestamp())  # 使用当前时间的时间戳
    if not start_ts:  # 如果没有提供开始时间戳
        start_ts = end_ts - CONFIG["METRIC_QUERY_WINDOW"]  # 使用配置的查询窗口时间计算开始时间
    
    # 将时间戳转换为datetime对象
    start_dt = datetime.fromtimestamp(start_ts)  # 将开始时间戳转换为datetime对象
    end_dt = datetime.fromtimestamp(end_ts)  # 将结束时间戳转换为datetime对象
    
    # 获取数据库信息
    if dbname:  # 如果提供了数据库名称
        db_info = await ctx.db.get_db_info(dbname)  # 异步获取数据库信息
        instance_id = db_info["instance_id"]  # 从数据库信息中提取实例ID
        service_type = db_info["service_type"]  # 从数据库信息中提取服务类型
    else:  # 如果只提供了实例ID
        # 通过实例ID获取数据库信息
        db_info = await ctx.db.get_instance_info(instance_id)  # 异步通过实例ID获取数据库信息
        service_type = db_info["service_type"]  # 从数据库信息中提取服务类型
        dbname = db_info["db_name"]  # 从数据库信息中提取数据库名称
    
    # 提取主机IP (用于节点监控)
    host_ip = await ctx.db.extract_host_ip(instance_id)  # 异步从实例ID中提取主机IP地址
    
    # 收集指标
    metrics = {}  # 初始化指标字典
    try:  # 尝试收集监控指标
        # 获取服务指标
        service_metrics = await ctx.prom.get_service_metrics(  # 异步获取服务级监控指标
            instance_id, service_type, start_ts, end_ts  # 传入实例ID、服务类型、开始和结束时间戳
        )
        metrics["service_metrics"] = service_metrics  # 将服务指标存储到指标字典中
        
        # 获取节点指标 (必须)
        if host_ip:  # 如果成功提取到主机IP
            node_metrics = await ctx.prom.get_node_metrics(  # 异步获取节点级监控指标
                host_ip, start_ts, end_ts  # 传入主机IP、开始和结束时间戳
            )
            metrics["node_metrics"] = node_metrics  # 将节点指标存储到指标字典中
        else:  # 如果未能提取到主机IP
            log.warning(f"无法为实例 {instance_id} 获取主机IP，节点指标将不可用")  # 记录警告日志
    except Exception as e:  # 捕获指标收集过程中的异常
        log.error(f"Prometheus查询失败: {e}", exc_info=True)  # 记录错误日志，包含异常堆栈信息
        raise RuntimeError("指标查询失败") from e  # 抛出运行时错误，保留原始异常信息
    
    # 生成提示
    time_range = f"{start_dt.isoformat()} 到 {end_dt.isoformat()}"  # 格式化时间范围字符串，使用ISO格式
    prompt_args = {  # 创建提示参数字典
        "dbname": dbname,  # 数据库名称
        "instance_id": instance_id,  # 实例ID
        "time_range": time_range,  # 时间范围
        "node_metrics_json": json.dumps(metrics.get("node_metrics", {}), ensure_ascii=False, indent=2),  # 将节点指标转换为格式化的JSON字符串
        "db_metrics_json": json.dumps(metrics.get("service_metrics", {}), ensure_ascii=False, indent=2)  # 将数据库指标转换为格式化的JSON字符串
    }
    
    prompt_tpl = prompt_registry.get("diagnose_db_performance")  # 从注册表获取诊断提示模板
    if not prompt_tpl:  # 如果模板不存在
        raise RuntimeError("找不到提示模板")  # 抛出运行时错误
    
    prompt_str = prompt_tpl.template.format(**prompt_args)  # 使用参数格式化提示模板
    
    return {  # 返回结果字典
        "dbname": dbname,  # 数据库名称
        "instance_id": instance_id,  # 实例ID
        "metrics": None,  # 指标数据设为None（在提示中已包含）
        "inspection_prompt": prompt_str  # 格式化后的巡检提示
    }

async def list_log_files(ctx: ApplicationContext, args: dict) -> dict:  # 异步函数，列出日志文件
    """列出日志文件"""  # 函数文档字符串
    log_dir = CONFIG["LOG_DIR"]  # 获取配置的日志目录路径
    if not log_dir.exists():  # 如果日志目录不存在
        log.warning(f"日志目录不存在: {log_dir}")  # 记录警告日志
        return {"log_files": []}  # 返回空的日志文件列表
    
    log_files = []  # 初始化日志文件列表
    for pattern in CONFIG["ALLOWED_LOG_PATTERNS"]:  # 遍历配置的允许访问的日志文件模式
        log_files.extend([  # 扩展日志文件列表
            f.name for f in log_dir.glob(pattern)  # 使用glob模式匹配文件，获取文件名
            if f.is_file() and f.stat().st_size < CONFIG["MAX_LOG_SIZE"]  # 筛选条件：是文件且大小小于限制
        ])
    
    log.info(f"找到 {len(log_files)} 个日志文件")  # 记录信息日志，显示找到的文件数量
    return {"log_files": sorted(log_files)}  # 返回排序后的日志文件列表

# 注册工具
tool_registry.register(ToolDefinition(  # 注册数据库信息查询工具
    name="query_db_info",  # 工具名称
    description="按数据库名称查询数据库信息",  # 工具描述
    input_schema={  # 输入参数模式定义
        "type": "object",  # 参数类型为对象
        "properties": {"dbname": {"type": "string"}},  # 属性定义：dbname为字符串类型
        "required": ["dbname"]  # 必需参数列表
    },
    handler=query_db_info  # 指定处理函数
))

tool_registry.register(ToolDefinition(  # 注册Prometheus指标查询工具
    name="query_prometheus_metrics",  # 工具名称
    description="按数据库名称或实例ID查询Prometheus指标",  # 工具描述
    input_schema={  # 输入参数模式定义
        "type": "object",  # 参数类型为对象
        "properties": {  # 属性定义
            "dbname": {"type": "string", "description": "数据库名称"},  # 数据库名称属性
            "instance_id": {"type": "string", "description": "实例ID (格式: ip:port 或 domain:port)"},  # 实例ID属性
            "start": {"type": "integer", "description": "北京时间开始时间戳"},  # 开始时间戳属性
            "end": {"type": "integer", "description": "北京时间结束时间戳"}  # 结束时间戳属性
        },
        "anyOf": [  # 参数组合要求：满足其中任一条件
            {"required": ["dbname"]},  # 条件1：需要数据库名称
            {"required": ["instance_id"]}  # 条件2：需要实例ID
        ]
    },
    handler=query_prometheus_metrics  # 指定处理函数
))

tool_registry.register(ToolDefinition(  # 注册日志文件列表工具
    name="list_log_files",  # 工具名称
    description="列出可用的日志文件",  # 工具描述
    input_schema={"type": "object", "properties": {}, "required": []},  # 输入参数模式：空对象，无必需参数
    handler=list_log_files  # 指定处理函数
))

# ---------------- Resource Management ----------------
def is_safe_path(base: Path, path: Path) -> bool:  # 路径安全检查函数
    """检查路径是否在基础目录内"""  # 函数文档字符串
    try:  # 尝试执行路径解析
        base = base.resolve()  # 解析基础路径为绝对路径
        path = path.resolve()  # 解析目标路径为绝对路径
        return base in path.parents or path == base  # 检查基础路径是否是目标路径的父目录或相等
    except Exception:  # 捕获所有解析异常
        return False  # 发生异常时返回False，拒绝访问

# ---------------- MCP Server Setup ----------------
app = Server("ops-mcp-demo-server", lifespan=lifespan)  # 创建MCP服务器实例，指定名称和生命周期管理函数
sse = SseServerTransport("/messages/")  # 创建服务器发送事件传输实例，指定消息端点路径

# 工具端点
@app.list_tools()  # 注册列出工具的端点处理器
async def list_tools() -> List[types.Tool]:  # 异步函数，返回工具列表
    """列出可用工具"""  # 函数文档字符串
    return tool_registry.list()  # 调用工具注册表的列表方法

@app.call_tool()  # 注册调用工具的端点处理器
async def call_tool(name: str, arguments: dict) -> List[types.TextContent]:  # 异步函数，执行指定工具
    """执行工具"""  # 函数文档字符串
    ctx: ApplicationContext = app.request_context.lifespan_context["ctx"]  # 从生命周期上下文获取应用上下文
    tool = tool_registry.get(name)  # 从工具注册表获取指定名称的工具
    if not tool:  # 如果工具不存在
        log.error(f"找不到工具: {name}")  # 记录错误日志，显示未找到的工具名称
        return [types.TextContent(type="text", text=f"找不到工具: {name}")]  # 返回错误信息的文本内容列表

    try:  # 尝试执行工具处理函数
        log.info(f"正在执行工具: {name}")  # 记录信息日志，显示正在执行的工具名称
        result = await tool.handler(ctx, arguments)  # 异步调用工具的处理函数，传入上下文和参数
        return [types.TextContent(  # 返回文本内容列表
            type="text",  # 内容类型为文本
            text=json.dumps(result, ensure_ascii=False, indent=2)  # 将结果转换为格式化的JSON字符串，不转义非ASCII字符，缩进2空格
        )]
    except Exception as e:  # 捕获工具执行过程中的所有异常
        log.exception(f"工具执行失败: {name}")  # 记录异常日志，包含完整的异常堆栈信息
        return [types.TextContent(  # 返回错误信息的文本内容列表
            type="text",  # 内容类型为文本
            text=json.dumps({"error": str(e)}, ensure_ascii=False)  # 将错误信息转换为JSON格式，不转义非ASCII字符
        )]


# 资源端点
@app.list_resources()  # 注册列出资源的端点处理器
async def list_resources() -> list[types.Resource]:  # 异步函数，返回资源列表
    """列出可用资源"""  # 函数文档字符串
    resources = []  # 初始化资源列表
    log_dir = CONFIG["LOG_DIR"]  # 获取配置的日志目录路径

    if not log_dir.exists():  # 如果日志目录不存在
        return []  # 返回空列表

    for pattern in CONFIG["ALLOWED_LOG_PATTERNS"]:  # 遍历配置的允许访问的文件模式
        for file_path in log_dir.glob(pattern):  # 使用glob模式匹配目录下的文件
            if file_path.is_file() and file_path.stat().st_size < CONFIG["MAX_LOG_SIZE"]:  # 如果是文件且大小小于限制
                rel_path = file_path.relative_to(CONFIG["BASE_DIR"])  # 计算相对于项目根目录的相对路径
                resources.append(  # 向资源列表添加新的资源对象
                    types.Resource(  # 创建资源对象
                        uri=f"file://{rel_path.as_posix()}",  # 设置文件URI，使用POSIX风格路径
                        name=file_path.name,  # 设置资源名称为文件名
                        description=f"日志文件: {file_path.name}",  # 设置资源描述
                        mimeType="text/plain"  # 设置MIME类型为纯文本
                    )
                )

    log.info(f"返回 {len(resources)} 个资源")  # 记录信息日志，显示找到的资源数量
    return resources  # 返回资源列表

@app.read_resource()  # 注册读取资源的端点处理器
async def read_resource(uri: str) -> str:  # 异步函数，读取指定URI的资源内容，返回字符串
    """读取资源内容"""  # 函数文档字符串
    if not uri.startswith("file://"):  # 如果URI不以file://开头
        raise ValueError("不支持的URI协议")  # 抛出值错误异常

    rel_path = uri[len("file://"):]  # 从URI中提取相对路径部分，去掉协议前缀
    abs_path = CONFIG["BASE_DIR"] / rel_path  # 拼接为绝对路径

    # 安全检查
    if not is_safe_path(CONFIG["LOG_DIR"], abs_path):  # 如果路径不安全（不在允许的目录内）
        raise PermissionError("不允许访问该路径")  # 抛出权限错误异常

    if not abs_path.exists():  # 如果文件不存在
        raise FileNotFoundError(f"文件不存在: {abs_path}")  # 抛出文件未找到异常

    try:  # 尝试读取文件
        # 带大小限制的读取
        file_size = abs_path.stat().st_size  # 获取文件大小
        if file_size > CONFIG["MAX_LOG_SIZE"]:  # 如果文件大小超过限制
            raise IOError("文件大小超过限制")  # 抛出IO错误异常

        with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:  # 以UTF-8编码打开文件，忽略编码错误
            return f.read()  # 读取并返回文件内容
    except Exception as e:  # 捕获读取过程中的所有异常
        log.error(f"资源读取失败: {e}", exc_info=True)  # 记录错误日志，包含异常堆栈信息
        raise RuntimeError("读取资源失败") from e  # 抛出运行时错误，保留原始异常信息


@app.list_resource_templates()  # 注册列出资源模板的端点处理器
async def list_resource_templates() -> List[types.ResourceTemplate]:  # 异步函数，返回资源模板列表
    """列出资源模板"""  # 函数文档字符串
    return [  # 返回包含资源模板的列表
        types.ResourceTemplate(  # 创建资源模板对象
            name="log_file_template",  # 模板名称
            description="访问服务器日志文件",  # 模板描述
            uriTemplate="file://logs/{filename}",  # URI模板，使用占位符
            mimeType="text/plain"  # MIME类型为纯文本
        )
    ]


# 提示端点
@app.list_prompts()  # 注册列出提示的端点处理器
async def list_prompts() -> List[types.Prompt]:  # 异步函数，返回提示列表
    """列出可用提示"""  # 函数文档字符串
    return prompt_registry.list()  # 调用提示注册表的列表方法


@app.get_prompt()  # 注册获取提示的端点处理器
async def get_prompt(name: str, arguments: Optional[dict] = None) -> str:  # 异步函数，获取格式化的提示文本
    """获取格式化后的提示文本"""  # 函数文档字符串
    tpl = prompt_registry.get(name)  # 从提示注册表获取指定名称的模板
    if not tpl:  # 如果模板不存在
        raise ValueError(f"找不到提示: {name}")  # 抛出值错误异常

    args = arguments or {}  # 如果没有提供参数则使用空字典
    missing = [arg.name for arg in tpl.arguments if arg.required and arg.name not in args]  # 检查缺失的必需参数
    if missing:  # 如果有缺失的必需参数
        raise ValueError(f"缺少必要参数: {', '.join(missing)}")  # 抛出值错误异常，列出缺失的参数

    return tpl.template.format(**args)  # 使用提供的参数格式化模板并返回


# ---------------- HTTP Endpoints ----------------
async def handle_sse(request):  # 异步函数，处理SSE连接请求
    """处理服务器发送事件(SSE)连接"""  # 函数文档字符串
    async with sse.connect_sse(request.scope, request.receive, request._send) as streams:  # 建立SSE连接，获取输入输出流
        await app.run(streams[0], streams[1], app.create_initialization_options())  # 运行MCP服务器，传入流和初始化选项


async def health_check(_) -> Response:  # 异步函数，健康检查端点，参数不使用故用下划线
    """健康检查端点"""  # 函数文档字符串
    return JSONResponse({"status": "ok", "services": ["db", "prometheus"]})  # 返回JSON响应，显示服务状态正常


async def readiness_check(_) -> Response:  # 异步函数，就绪检查端点，参数不使用故用下划线
    """检查数据库和Prometheus的连接状态"""  # 函数文档字符串
    ctx: ApplicationContext = app.lifespan_context.get("ctx", None)  # 从生命周期上下文获取应用上下文
    status = {  # 创建状态字典
        "db": ctx.db.is_connected() if ctx else False,  # 如果上下文存在则检查数据库连接状态，否则为False
        "prometheus": ctx.prom.is_connected() if ctx else False  # 如果上下文存在则检查Prometheus连接状态，否则为False
    }
    return JSONResponse(status)  # 返回包含连接状态的JSON响应


def create_app() -> Starlette:  # 函数，创建并配置Starlette应用，返回应用实例
    """创建Starlette应用"""  # 函数文档字符串
    return Starlette(  # 创建并返回Starlette应用实例
        routes=[  # 配置路由列表
            Route("/sse", handle_sse),  # SSE连接端点路由
            Mount("/messages/", app=sse.handle_post_message),  # 消息处理端点挂载
            Route("/health", health_check),  # 健康检查端点路由
            Route("/ready", readiness_check)  # 就绪检查端点路由
        ],
        middleware=[  # 配置中间件列表
            Middleware(  # 添加CORS中间件
                CORSMiddleware,  # CORS中间件类
                allow_origins=CONFIG["ALLOWED_ORIGINS"],  # 允许的来源列表
                allow_methods=["*"],  # 允许所有HTTP方法
                allow_headers=["*"],  # 允许所有请求头
                expose_headers=["*"]  # 暴露所有响应头
            )
        ],
        debug=False  # 关闭调试模式
    )


# ---------------- Main Entry Point ----------------
if __name__ == "__main__":  # 如果这个脚本是直接运行的（不是被导入的）
    import uvicorn  # 导入uvicorn ASGI服务器

    uvicorn.run(  # 运行uvicorn服务器
        create_app(),  # 传入创建的应用实例
        host=CONFIG["HOST"],  # 使用配置的主机地址
        port=CONFIG["PORT"],  # 使用配置的端口号
        log_config=None,  # 使用我们自己的日志设置，不使用uvicorn的默认日志配置
        timeout_keep_alive=60  # 设置keep-alive超时时间为60秒
    )